package org.dftproject.genesis.ui.pages.circlediagram;

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dftproject.genesis.data.genealogy.GenealogyUtils;
import org.dftproject.genesis.data.genealogy.IName;
import org.dftproject.genesis.data.genealogy.IPerson;
import org.dftproject.genesis.data.genealogy.impl.MergedPerson;
import org.dftproject.genesis.ui.Activator;
import org.dftproject.genesis.ui.figures.Caption;
import org.dftproject.genesis.ui.pages.AbstractPagePart;
import org.dftproject.genesis.ui.pages.IPageInput;
import org.dftproject.genesis.ui.pages.IPageSite;
import org.dftproject.genesis.ui.pages.PageInitException;
import org.dftproject.genesis.ui.pages.PageManager;
import org.dftproject.genesis.ui.pages.pedigree.PedigreePage;
import org.dftproject.genesis.ui.pages.pedigree.PedigreePageInput;
import org.dftproject.genesis.ui.pages.pedigree.icon.PedigreeIconFigure;
import org.dftproject.genesis.ui.pages.summary.SummaryPage;
import org.dftproject.genesis.ui.pages.summary.SummaryPageInput;
import org.dftproject.genesis.ui.widgets.gallery.Gallery;
import org.eclipse.draw2d.BorderLayout;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.MarginBorder;
import org.eclipse.draw2d.Panel;
import org.eclipse.draw2d.ToolbarLayout;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;

public class CircleDiagramPage extends AbstractPagePart {
	
	private static final Log log = LogFactory.getLog(CircleDiagramPage.class);

	public static final String ID = CircleDiagramPage.class.getName();

	private static final int numThreads = 2;
	private ThreadPoolExecutor threadPool;

	private final Set<MergedPerson> alreadyVisited = Collections.synchronizedSet(new HashSet<MergedPerson>());
	
	private boolean updateScheduled;
	private final Queue<Runnable> updateQueue = new LinkedList<Runnable>();
	private final Runnable uiRunnable = new Runnable() {

		public void run() {
			updateScheduled = false;
			update();
		}

	};

	private FigureCanvas canvas;
	private CircleDiagramFigure circleDiagram;
	
	private MergedPerson person;

	public void init(IPageSite site, IPageInput input) throws PageInitException {
		if (!(input instanceof CircleDiagramPageInput))
			throw new PageInitException("input must be a " + CircleDiagramPageInput.class.getName());
		this.setSite(site);
		this.setInput(input);
		
		threadPool = new ThreadPoolExecutor(numThreads, numThreads, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
		
		person = getMergedPerson(getCircleDiagramPageInput().getPerson());		
	}

	protected CircleDiagramPageInput getCircleDiagramPageInput() {
		return (CircleDiagramPageInput) getInput();
	}
	
	protected MergedPerson getMergedPerson(IPerson person) {
		if (person instanceof MergedPerson || person == null)
			return (MergedPerson) person;
		return new MergedPerson(person);
	}

	@Override
	public void createPartControl(Composite parent) {
		Composite container = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout();
		layout.marginWidth = layout.marginHeight = layout.marginTop = layout.marginRight = layout.marginBottom = layout.marginLeft = layout.horizontalSpacing = 0;
		layout.numColumns = 2;
		container.setLayout(layout);

		createGallery(container);

		Composite body = new Composite(container, SWT.NONE);
		layout = new GridLayout();
		layout.marginWidth = layout.marginHeight = layout.marginTop = layout.marginRight = layout.marginBottom = layout.marginLeft = layout.verticalSpacing = 0;
		body.setLayout(layout);
		body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		createContents(body);
	}

	protected void createGallery(Composite parent) {
		Gallery gallery = new Gallery(parent);
		gallery.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true));

		gallery.addItem(Activator.getImageDescriptor("icons/user.png").createImage(), false, "Summary", new Runnable() {

			public void run() {
				IWorkbench workbench = PlatformUI.getWorkbench();
				IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
				IWorkbenchPage workbenchPage = window.getActivePage();
				PageManager.getDefault().openPage(workbenchPage, new SummaryPageInput(person), SummaryPage.ID, true, false);
			}

		});
		gallery.addItem(new PedigreeIconFigure(), false, "Pedigree", new Runnable() {

			public void run() {
				IWorkbench workbench = PlatformUI.getWorkbench();
				IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
				IWorkbenchPage workbenchPage = window.getActivePage();
				PageManager.getDefault().openPage(workbenchPage, new PedigreePageInput(person), PedigreePage.ID, true, false);
			}

		});
		gallery.addItem(new CircleDiagramIconFigure(), true, "Circle Diagram", null);
	}

	protected void createContents(Composite parent) {
		// Create the main Draw2D canvas

		canvas = new FigureCanvas(parent);
		canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

		Panel container = new Panel();
		container.setBackgroundColor(ColorConstants.listBackground);
		BorderLayout layout = new BorderLayout();
		container.setLayoutManager(layout);
		canvas.setContents(container);

		circleDiagram = new CircleDiagramFigure();
		ToolbarLayout circleDiagramLayout = new ToolbarLayout(false);
		circleDiagram.setLayoutManager(circleDiagramLayout);
		container.add(circleDiagram, BorderLayout.CENTER);

		Caption title = new Caption("Circle Diagram");
		title.setBorder(new MarginBorder(10, 10, 0, 10));
		circleDiagram.add(title);

		IName name = GenealogyUtils.getName(person);
		if (name != null) {
			Label subtitle = new Label(GenealogyUtils.stringFromName(name));
			subtitle.setBorder(new MarginBorder(0, 10, 0, 10));
			circleDiagram.add(subtitle);
		}
		
		threadPool.execute(new Runnable() {
			
			public void run() {
				final CircleDiagramNode node = new CircleDiagramNode(person);
				addParents(node);
				
				scheduleUIUpdate(new Runnable() {

					public void run() {
						circleDiagram.setPersonNode(node);
					}
					
				});
			}
			
		});
	}
	
	protected void addParents(final CircleDiagramNode child) {
		log.info(child.getPerson());
		threadPool.execute(new Runnable() {
			
			public void run() {
				MergedPerson father = getMergedPerson(GenealogyUtils.getFather(child.getPerson()));
				if (father == null || alreadyVisited.add(father)) {
					final CircleDiagramNode left = (father == null ? null : new CircleDiagramNode(father));
					if (left != null)
						addParents(left);
					scheduleUIUpdate(new Runnable() {

						public void run() {
							circleDiagram.setFatherNode(child, left);
						}
						
					});
				} else {
					log.info(father.toString() + " already seen");
				}
				
				MergedPerson mother = getMergedPerson(GenealogyUtils.getMother(child.getPerson()));
				if (mother == null || alreadyVisited.add(mother)) {
					final CircleDiagramNode right = (mother == null ? null : new CircleDiagramNode(mother));
					if (right != null)
						addParents(right);
					scheduleUIUpdate(new Runnable() {

						public void run() {
							circleDiagram.setMotherNode(child, right);
						}
						
					});
				} else {
					log.info(mother.toString() + " already seen");
				}
			}
			
		});
	}

	protected void scheduleUIUpdate(Runnable item) {
		synchronized (updateQueue) {
			updateQueue.add(item);
			if (!updateScheduled) {
				updateScheduled = true;
				Display.getDefault().asyncExec(uiRunnable);
			}
		}
	}

	protected void update() {
		synchronized (updateQueue) {
			Runnable item;
			while ((item = updateQueue.poll()) != null) {
				item.run();
			}
		}
	}

	@Override
	public void dispose() {
	}

	@Override
	public void setFocus() {
		canvas.setFocus();
	}

}
